'''
Created on Jul 4, 2009
@author: mjbommar
'''

import datetime, sqlite3, os.path
import os, os.path, sys, glob, re
from tfaces.collection.Run import *
from tfaces.collection.util import *

try:
    import numpy
    import scipy
    import pylab
    import igraph
except:
    raise Exception("tfaces.collection.Experiment: numpy, scipy, pylab, igraph must all be installed")


class Experiment(object):
    '''
    Experiments are containers of runs.
    '''

    def __init__(self, experimentDirectory='.', reportFilePattern='report.[0-9]+', runs=[], name=None):
        '''
        The Experiment constructor takes a list of Run objects and an experiment name.
        
        If the constructor is called without arguments, Runs can be added later.
        The default name is "experiment-%Y%m%d%H%M%S"
        '''
        # Set up the experiment name
        if name:
            self.name = name
        else:
            self.name = "experiment-%s" % datetime.date.strftime(datetime.datetime.now(), "%Y%m%d%H%M%S")
        
        self.runs = []
        if len(runs) > 0:
            self.addRuns(runs)

        self.parameterKeys = []
        self.parameterValues = {}
        self.outputKeys = []
        
        # Pre-compiled report file RE
        self.reportFileRE = re.compile(reportFilePattern)
        
        # Set the experiment directory, find the run files, and process them
        self.setExperimentDirectory(experimentDirectory)


        self.db = sqlite3.Connection
    
    def __repr__(self):
        '''
        Return a string representation of the Experiment.
        '''
        return 'tfaces.collection.Experiment: %d parameters, %d outputs, %d runs' % (len(self.parameterKeys), len(self.outputKeys), len(self.runs))
        
    
    def updateParameterValues(self, parameters):
        '''
        Update the set of observed values for each parameter from the given parameter dictionary.
        '''
        for (parameter, value) in parameters.iteritems():
            if self.parameterValues.has_key(parameter):
                self.parameterValues[parameter].add(value)
            else:
                self.parameterValues[parameter] = set([value])

    def addRuns(self, runs):
        '''
        The addRuns method checks that the given parameter and output keys match existing
        runs, and then adds these runs.
        
        Note that it does not check output types.
        '''
        
        for run in runs:
            if not isinstance(run, Run):
                raise Exception("Experiment::addRuns: runs must be an iterable containing Run objects")
            
            if len(self.runs) == 0:
                self.parameterKeys = run.parameters.keys()
                if isinstance(run, TimeSeriesRun):
                    self.outputKeys = run.outputs.keys()
            else:
                if self.parameterKeys != run.parameters.keys():
                    raise Exception("Experiment::addRuns: parameter keys must match across all runs")
                
                if isinstance(run, TimeSeriesRun):
                    if self.outputKeys != run.outputs.keys():
                        raise Exception("Experiment::addRuns: output keys must match across all runs")

            self.updateParameterValues(run.parameters)
            self.runs.append(run)

    def setExperimentDirectory(self, path):
        '''
        Set the Drone experiment directory.
        '''
        # Convert to an absolute path and check that it exists
        path = os.path.abspath(path)
    
        # Check that the path exists
        if not os.path.exists(path):
            raise Exception("Experiment::setExperimentDirectory: path %s does not exist" % path)
    
        # Normalize the path
        if not path.endswith(os.sep):
            path += os.sep
        
        self.path = path
    
    def lineSplit(self, line):
        """
        This method intelligently splits a line by guessing
        the delimiter and cleaning it up.
        """

        # Comma separation
        if line.find(',') >= 0:
            line = line.strip(',')
            tokens = line.split(',')
        # Whitespace separation
        else:
            tokens = line.split()
        
        # Return the stripped fields
        return map(str.strip, tokens)

    def findRuns(self, path=None):
        """
        This method finds all run report files that match the specified form.
        """
        if not path:
            path = self.path
        
        # Set up the list of run files and find all files in the current directory
        runFiles = []
        pathFiles = glob.glob(path + '*')
        
        # Check to see which files match
        for pathFile in pathFiles:
            fileName = os.path.basename(pathFile)
            if self.reportFileRE.match(fileName):
                runFiles.append(pathFile)
        
        # Recursively search the directories under this path
        for entry in glob.glob(path + '*'):
            if os.path.isdir(entry):
                dirResults = self.findRuns(entry + os.sep)

                if len(dirResults) > 0:
                    runFiles.extend(dirResults)

        # Raise an exception if no reports were found
        if len(runFiles) == 0:
            raise Exception("Experiment::findRuns: no report files found matching report pattern %s" % self.reportFileRE.pattern)
        
        return runFiles
